/* Copyright (c) 2008, The Visigoth Software Society
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notices,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name Visigoth Software Society, FreeCC, Sun Microsystems, Inc. 
 *       nor the names of any other contributors may be used to endorse 
 *       or promote products derived from this software without specific prior written 
 *       permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.visigoths.freecc.output.java;

import java.io.*;
import java.util.*;
import org.visigoths.freecc.parser.*;
import org.visigoths.freecc.parser.tree.*;
import static org.visigoths.freecc.parser.FreeCCConstants.*;


public class JavaFormatter {
    
    private Token currentToken, lastToken, nextToken;
    private BaseNode parent;
    private StringBuilder buf = new StringBuilder();
    private String indent = "    ";
    private String currentIndent = "";
    
    public JavaFormatter() {
    }
    
    public String format(Reader input) throws IOException, ParseException {
        FreeCCParser parser = new FreeCCParser(input);
        CompilationUnit cu = parser.CompilationUnit();
        input.close();
        return format(cu);
    }
    
    public String format(BaseNode code) {
        buf = new StringBuilder();
        ListIterator<Token> iterator = code.getTokens().listIterator();
        while (iterator.hasNext()) {
            this.lastToken = currentToken;
            currentToken = iterator.next();
            parent = (BaseNode) currentToken.jjtGetParent();
            if (iterator.hasNext()) {
                nextToken = iterator.next();
                iterator.previous();
            } else {
                nextToken = null;
            }
            handleToken();
        }
        return buf.toString();
    }
    
    private void handleToken() {
        switch (currentToken.kind) {
            case PACKAGE :
                buf.append("\n");
            case IMPORT :
            case CLASS :
            case INTERFACE :
            case FINAL :
            case STATIC :
            case PUBLIC :
            case PROTECTED :
            case PRIVATE :
            case STRICTFP :
            case VOLATILE :
            case TRANSIENT :
            case NATIVE :
            case CASE :
            case NEW :
            case RETURN :
            case THROW : 
            case IF :
            case ELSE :
            case COMMA :
                buf.append(currentToken);
                buf.append(' ');
                break;
            case VOID : 
            case BOOLEAN :
            case BYTE :
            case CHAR :
            case INT :
            case SHORT :
            case LONG :
            case FLOAT :
            case DOUBLE :
                buf.append(currentToken);
                if (!(parent instanceof CastExpression || parent instanceof TypeParameter))
                    buf.append(' ');
                break;
            case INSTANCEOF :
            case THROWS : 
            case EXTENDS :
            case IMPLEMENTS :
                buf.append(' ');
                buf.append(currentToken);
                buf.append(' ');
                break;
            case LBRACE :
                handleOpenBrace();
                break;
            case RBRACE :
                handleCloseBrace();
                break;
            case COLON :
                handleColon();
                break;
            case SEMICOLON :
                handleSemicolon();
                break;
            case RPAREN :
                handleCloseParen();
                break;
            case FORMAL_COMMENT :
            case MULTI_LINE_COMMENT :
            case SINGLE_LINE_COMMENT :
                handleComment();
                break;
            case CONTINUE :
            case BREAK : 
            case IDENTIFIER :
                buf.append(currentToken);
                if (nextToken != null && nextToken.kind == IDENTIFIER) {
                    buf.append(' ');
                }
                break;
            default:
                buf.append(currentToken);
        }
    }
    
    
    private void handleCloseParen() {
        
        buf.append(')');
        if (parent instanceof Annotation) {
            newLine();
        }
        
    }
    
    private void handleSemicolon() {
        buf.append(';');
        if (parent instanceof ForStatement) {
            buf.append(' ');
        } else {
            buf.append("\n");
            if (nextToken != null && nextToken.kind != RBRACE)
                buf.append(currentIndent);
        }
    }
    
    private void handleColon() {
        if (parent instanceof ConditionalOrExpression)
            buf.append(" : ");
        else 
            buf.append(':');
    }
    
    private void handleOpenBrace() {
        if (parent instanceof ArrayInitializer) {
            buf.append('{');
            return;
        }
        buf.append(' ');
        buf.append('{');
        indent();
    }
    
    private void handleCloseBrace() {
        if (parent instanceof ArrayInitializer) {
            buf.append('}');
            return;
        }
        dedent();
        buf.append('}');
        buf.append('\n');
        if (parent instanceof TypeDeclaration 
            || parent instanceof ConstructorDeclaration
            || parent.jjtGetParent() instanceof MethodDeclaration) 
        {
            buf.append("\n");
        }
        if (nextToken !=null && nextToken.kind != RBRACE) {
            buf.append(currentIndent);
        }
    }
     
    private void handleComment() {
        if (lastToken != null && lastToken.kind != FORMAL_COMMENT 
                 && lastToken.kind != MULTI_LINE_COMMENT && lastToken.kind != SINGLE_LINE_COMMENT) {
            insertInterveningWhitespace(lastToken, currentToken);
        }
        buf.append(currentToken); // TODO: handle this
        if (currentToken.kind == FORMAL_COMMENT) {
            newLine();
            return;
        }
    }
    
    private void insertInterveningWhitespace(Token first, Token second) {
        if (first == null || second == null) return;
        if (!first.getInputSource().equals(second.getInputSource())) return;
        int numLines = second.getBeginLine() - first.getEndLine();
        if (numLines >0) {
            for (int i=0; i<numLines; i++) {
                buf.append('\n');
            }
            for (int i=0; i<second.getBeginColumn() -1; i++) {
                buf.append(' ');
            }
        } else {
            int numSpaces = second.getBeginColumn() - first.getEndColumn() -1;
            for (int i=0; i<numSpaces; i++) {
                buf.append(' ');
            }
        }
    }
    
    private void newLine() {
        buf.append("\n");
        buf.append(currentIndent);
    }
    
    private void indent() {
        currentIndent += indent;
        newLine();
    }
    
    private void dedent() {
        currentIndent = currentIndent.substring(0, currentIndent.length() -indent.length());
        buf.append(currentIndent);
    }
    
    private String getCurrentLine() {
        int i = buf.length();
        if (i == 0 || buf.charAt(i-1) == '\n') 
            return "";
        while (i>0) {
            if (buf.charAt(--i) == '\n') break; 
        }
        if (buf.charAt(i) == '\n') ++i;
        return buf.substring(i);
    }
}
